package org.jfk.core.utils;

import java.sql.Timestamp;

public class GeoUtils {
	
	static double pi = 3.14159265358979324;
	static double a = 6378245.0;
	static double ee = 0.00669342162296594323;
	public final static double x_pi = 3.14159265358979324 * 3000.0 / 180.0;	

	/**
	 * 计算地球上任意两点(经纬度)距离
	 * 
	 * @param long1
	 *            第一点经度
	 * @param lat1
	 *            第一点纬度
	 * @param long2
	 *            第二点经度
	 * @param lat2
	 *            第二点纬度
	 * @return 返回距离 单位：米
	 */
	public static double distance(double lat1,double long1,
			double lat2,  double long2) {
		
		if (lat1==lat2 && long1==long2){
			return 0;
		}
		
		double a, b, R;
		R = 6378137; // 地球半径
		lat1 = lat1 * Math.PI / 180.0;
		lat2 = lat2 * Math.PI / 180.0;
		a = lat1 - lat2;
		b = (long1 - long2) * Math.PI / 180.0;
		double d;
		double sa2, sb2;
		sa2 = Math.sin(a / 2.0);
		sb2 = Math.sin(b / 2.0);
		d = 2* R* Math.asin(Math.sqrt(sa2 * sa2 + Math.cos(lat1)
						* Math.cos(lat2) * sb2 * sb2));
		return d;
	}

	/**
	 * 判定给定点是否在给定圆形区域之内
	 * @param plat
	 * @param plng
	 * @param clat
	 * @param clng
	 * @param radius
	 * @return
	 */
	public static boolean isInCircle(double plat,double plng,
			double clat,  double clng,double radius){
		double dis = distance(plat,plng,clat,clng);
		return dis <= radius;
	}
	
	/**
	 * 判定给定点是否在给定矩形区域之内
	 * @param plat
	 * @param plng
	 * @param nelat
	 * @param nelng
	 * @param swlat
	 * @param swlng
	 * @return
	 */
	public static boolean isInRectangle(double plat, double plng, double nelat,
			double nelng, double swlat, double swlng) {

		if (plat > nelat) {
			return false;
		}
		if (plat < swlat) {
			return false;
		}
		if (plng > nelng) {
			return false;
		}
		if (plng < swlng) {
			return false;
		}
		return true;
	}

	
    /**
    *** Returns true if the specified point is inside the polygon formed by
    *** a specified list of points <br>
    *** NOTE: The list of points MUST represent a <strong>closed</strong> polygon.
    *** @param gp The point to check if is inside the polygon
    *** @return True if the specified point is inside the polygon
    **/
    public static boolean isPointInside(GeoPoint gp, GeoPoint... pp)
    {

        /* quick argument validation */
        if ((gp == null) || (pp == null)) {
            return false;
        }

        /* close polygon (make sure last point is same as first) */
        pp = closePolygon(pp);

        // Uses "Winding Number" algorithm
        // Notes: 
        //  - This is a very simple algorithm that compares the number of downward vectors
        //    with the number of upward vectors surrounding a specified point.  
        //  - This algorithm was designed for a 2D plane and will fail for a curved surface
        //    where the distance between points is great.
        // Observations:
        //  - It appears that the state borders may have been defined by simple X/Y cooridinated
        //    based on latitude/longitude values.  The simple cases are states bordered by 
        //    constant longitudes or latitudes.
        int wn = 0;                                             // the winding number counter
        for (int i = 0; i < pp.length - 1; i++) {               // edge from V[i] to V[i+1]
            if (pp[i].getY() <= gp.getY()) {                    // start y <= P.y
                if (pp[i+1].getY() > gp.getY()) {               // an upward crossing
                    if (_isLeft(pp[i],pp[i+1],gp) > 0.0) {  // P left of edge
                        ++wn;                                   // have a valid up intersect
                    }
                }
            } else {                                            // start y > P.y (no test needed)
                if (pp[i+1].getY() <= gp.getY()) {              // a downward crossing
                    if (_isLeft(pp[i],pp[i+1],gp) < 0.0) {  // P right of edge
                        --wn;                                   // have a valid down intersect
                    }
                }
            }
        }
        return (wn == 0)? false : true; // wn==0 if point is OUTSIDE

    }
    
    /**
    *** Returns true if the specified points represent a closed polygon
    *** @param gp The set of points representing a polygon
    *** @return True if the represented polygon is closed
    **/
    public static boolean isClosed(GeoPoint gp[])
    {
        if (gp == null) {
            // no points
            return false;
        } else
        if (gp.length < 3) {
            // below minimum points for a closed polygon
            return false;
        } else {
            // first point equals last point?
            GeoPoint gp0 = gp[0];
            GeoPoint gpN = gp[gp.length - 1];
            return gp0.equals(gpN);
        }
    }
    
    
    /**
    *** Closes the polygon represented by the list of points
    *** @return A closed polygon
    **/
    public static GeoPoint[] closePolygon(GeoPoint gp[])
    {
    	if (isClosed(gp)){
    		return gp;
    	}
    	
        if (ListTools.isEmpty(gp)) {
            // null/empty, return as-is
            return gp;
        } else
        if (gp.length < 3) {
            // invalid number of points, return as-is
            return gp;
        } else {
            GeoPoint gp0 = gp[0];
            GeoPoint gpN = gp[gp.length - 1];
            if (gp0.equals(gpN)) {
                // already closed
                return gp;
            } else {
                // close and return new array
                return ListTools.add(gp, gp0);
            }
        }
    }
    
	/**
	 *** Tests if the point, <code>gpC</code>, is Left|On|Right of an infinite
	 * line formed by <code>gp0</code> and <code>gp1</code>
	 *** 
	 * @param gp0
	 *            First point forming the line
	 *** @param gp1
	 *            Second point forming the line
	 *** @return <ul>
	 ****         <li>>0 for gpC left of the line through gp0 and P1</li>
	 ***         <li>=0 for gpC on the line</li>
	 ***         <li><0 for gpC right of the line</li>
	 *         </ul>
	 *** @see "The January 2001 Algorithm 'Area of 2D and 3D Triangles and Polygons'"
	 **/
	private static double _isLeft(GeoPoint gp0, GeoPoint gp1, GeoPoint gpC) {
		double val = (gp1.getX() - gp0.getX()) * (gpC.getY() - gp0.getY())
				- (gpC.getX() - gp0.getX()) * (gp1.getY() - gp0.getY());
		return val;
	}

	
	/**
	 * 利用海伦公式近似计算点到点段的距离
	 * @param plat
	 * @param plng
	 * @param lat1
	 * @param lng1
	 * @param lat2
	 * @param lng2
	 * @return
	 */
	public static  double distancePoint2Line(double plat,double plng,
			double lat1,  double lng1,
			double lat2,  double lng2){
		
		double ab = distance(lat1,lng1,lat2,lng2);
		double ac = distance(lat1,lng1,plat,plng);
		double cb = distance(plat,plng,lat2,lng2);
		double p = (ab+ac+cb)/2;
		double s = Math.sqrt(p*(p-ab)*(p-ac)*(p-cb));
		double h = s*2/ab;
		return h;
		
	}
	
	
	/**
	 * WGS-百度
	 * @param lat
	 * @param lon
	 * @return
	 */
	public static double[] wgs2bd(double lat, double lon) {
	       double[] wgs2gcj = wgs2gcj(lat, lon);
	       double[] gcj2bd = gcj2bd(wgs2gcj[0], wgs2gcj[1]);
	       return gcj2bd;
	}

	/**
	 * 国测局-百度
	 * @param lat
	 * @param lon
	 * @return
	 */
	public static double[] gcj2bd(double lat, double lon) {
	       double x = lon, y = lat;
	       double z = Math.sqrt(x * x + y * y) + 0.00002 * Math.sin(y * x_pi);
	       double theta = Math.atan2(y, x) + 0.000003 * Math.cos(x * x_pi);
	       double bd_lon = z * Math.cos(theta) + 0.0065;
	       double bd_lat = z * Math.sin(theta) + 0.006;
	       return new double[] { bd_lat, bd_lon };
	}

	/**
	 * 百度-国测局
	 * @param lat
	 * @param lon
	 * @return
	 */
	public static double[] bd2gcj(double lat, double lon) {
	       double x = lon - 0.0065, y = lat - 0.006;
	       double z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * x_pi);
	       double theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * x_pi);
	       double gg_lon = z * Math.cos(theta);
	       double gg_lat = z * Math.sin(theta);
	       return new double[] { gg_lat, gg_lon };
	}

	/**
	 * WGS-国测局
	 * @param lat
	 * @param lon
	 * @return
	 */
	public static double[] wgs2gcj(double lat, double lon) {
	       double dLat = transformLat(lon - 105.0, lat - 35.0);
	       double dLon = transformLon(lon - 105.0, lat - 35.0);
	       double radLat = lat / 180.0 * pi;
	       double magic = Math.sin(radLat);
	       magic = 1 - ee * magic * magic;
	       double sqrtMagic = Math.sqrt(magic);
	       dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * pi);
	       dLon = (dLon * 180.0) / (a / sqrtMagic * Math.cos(radLat) * pi);
	       double mgLat = lat + dLat;
	       double mgLon = lon + dLon;
	       double[] loc = { mgLat, mgLon };
	       return loc;
	}
	
	/**
	 * 百度-WGS
	 * @param lat
	 * @param lon
	 * @return
	 */
	public static double[] bd2wgs(double lat, double lon){
		double[] a = wgs2bd(lat,lon);
		double rlat = 2*lat-a[0];
		double rlon = 2*lon-a[1];
		double[] b = {rlat,rlon};
		return b;
		
	}

	private static double transformLat(double lat, double lon) {
	       double ret = -100.0 + 2.0 * lat + 3.0 * lon + 0.2 * lon * lon + 0.1 * lat * lon + 0.2 * Math.sqrt(Math.abs(lat));
	       ret += (20.0 * Math.sin(6.0 * lat * pi) + 20.0 * Math.sin(2.0 * lat * pi)) * 2.0 / 3.0;
	       ret += (20.0 * Math.sin(lon * pi) + 40.0 * Math.sin(lon / 3.0 * pi)) * 2.0 / 3.0;
	       ret += (160.0 * Math.sin(lon / 12.0 * pi) + 320 * Math.sin(lon * pi  / 30.0)) * 2.0 / 3.0;
	       return ret;
	}

	private static double transformLon(double lat, double lon) {
	       double ret = 300.0 + lat + 2.0 * lon + 0.1 * lat * lat + 0.1 * lat * lon + 0.1 * Math.sqrt(Math.abs(lat));
	       ret += (20.0 * Math.sin(6.0 * lat * pi) + 20.0 * Math.sin(2.0 * lat * pi)) * 2.0 / 3.0;
	       ret += (20.0 * Math.sin(lat * pi) + 40.0 * Math.sin(lat / 3.0 * pi)) * 2.0 / 3.0;
	       ret += (150.0 * Math.sin(lat / 12.0 * pi) + 300.0 * Math.sin(lat / 30.0 * pi)) * 2.0 / 3.0;
	       return ret;
	}	
	
	public static void main(String[] args){
		System.out.println(distance(26.3605650000,112.8751020000,26.3582930000,
				112.8746270000));
		

		
		System.out.println(distancePoint2Line(26.3682930000,
				112.8746270000,26.3605650000,112.8751020000,26.3582930000,
				112.8746270000));
		
		System.out.println(new Timestamp(System.currentTimeMillis()-20*24*60*60*1000));
		
		String[] s = ",Urgency,OverSpeed,Tired,DangerPro,GpsModeError,GpsAntBreak,GpsAntShort,LowPower,NoPower,".split(",");
		
		for (int i = 0 ;i<s.length;i++){
			System.out.println(s[i]);
		}
		
		double[] a = bd2wgs(39.90923,116.397428);
		System.out.println(a[0]);
		System.out.println(a[1]);

		a = wgs2bd(30.396467,114.8894);
		System.out.println(a[0]);
		System.out.println(a[1]);
	
	}
}
